//
//  LSDownloadManager.m
//  LibraryStorm
//
//  Created by Anh Nguyen on 3/31/14.
//  Copyright (c) 2014 3ATeam. All rights reserved.
//

#import "LSDownloadManager.h"

@interface LSDownloadManager ()

@property (atomic, strong) NSMutableSet *requestURLs;
@property (atomic, strong) NSMutableSet *requestOperationsDownloading;
@property (atomic, strong) NSMutableSet *requestOperationsQueue;
@property (atomic, strong) NSMutableSet *requestOperationsPaused;
@end
@implementation LSDownloadManager
#pragma mark - Initialized LSDownloadManager
+ (LSDownloadManager *)sharedInstance
{
    static dispatch_once_t once;
    static LSDownloadManager *sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    
    return sharedInstance;
}

- (id)init
{
    self = [super init];
    if (self) {
        _requestURLs = [NSMutableSet set];
        _requestOperationsQueue = [NSMutableSet set];
        _requestOperationsDownloading = [NSMutableSet setWithCapacity:5];
        _requestOperationsPaused = [NSMutableSet set];
        _maxRunningTaskCount = 5;
        _shouldResume = YES;
    }
    
    return self;
}

#pragma mark - Property Of Operations
- (NSSet *)downloadingOperations
{
    return [NSSet setWithSet:self.requestOperationsDownloading];
}

- (NSSet *)operationsInQueue
{
    return [NSSet setWithSet:self.requestOperationsQueue];
}

- (NSSet *)pausedOperations
{
    return [NSSet setWithSet:self.requestOperationsPaused];
}

#pragma mark - AFDownloadRequestOperation
- (AFDownloadRequestOperation *)addURL:(NSURL *)url fileStorePath:(NSString *)destinationFilePath
{
    if (!url && url.absoluteString.length == 0) {
        return nil;
    }
    
    if ([self.requestURLs containsObject:url]) {
        NSLog(@"LSDownloadManager: the url already existed. %@", url);
        return nil;
    }
    
    AFDownloadRequestOperation *downloadOperation = [[AFDownloadRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url] targetPath:destinationFilePath shouldResume:self.shouldResume];
    if (downloadOperation == nil) {
        return nil;
    }
    [self.requestOperationsDownloading addObject:downloadOperation];
    [self.requestOperationsQueue addObject:downloadOperation];
    [self.requestURLs addObject:url];
    
    return downloadOperation;
}

- (AFDownloadRequestOperation *)findOperationWithURL:(NSURL *)url
{
    AFDownloadRequestOperation *operation = nil;
    
#define _RFDownloadManagerFindOperationInSet(RequrestOperations)\
for (operation in RequrestOperations) {\
if ([operation.request.URL.path isEqualToString:url.path]) {\
return operation;\
}\
}
    
    _RFDownloadManagerFindOperationInSet(self.requestOperationsDownloading)
    _RFDownloadManagerFindOperationInSet(self.requestOperationsQueue)
    _RFDownloadManagerFindOperationInSet(self.requestOperationsPaused)
#undef _RFDownloadManagerFindOperationInSet
    return nil;
}

- (void)setupDownloadOperation:(AFDownloadRequestOperation *)downloadOperation
{
    __weak AFDownloadRequestOperation *aOperation = downloadOperation;
    aOperation.deleteTempFileOnCancel = YES;
    [aOperation setProgressiveDownloadProgressBlock:^(AFDownloadRequestOperation *operation, NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {
        if ([self.delegate respondsToSelector:@selector(downloadManager:operationStateUpdate:downloadProgress:)]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                float progress = (totalBytesReadForFile/(float)totalBytesExpectedToReadForFile);
                [self.delegate downloadManager:self operationStateUpdate:operation downloadProgress:progress];
            });
        }
    }];
    
    [aOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        [self onFileDownloadOperationComplete:operation success:YES];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        [self onFileDownloadOperationComplete:operation success:NO];
    }];
}

- (void)onFileDownloadOperationComplete:(AFHTTPRequestOperation *)operation success:(BOOL)success {
    [self.requestOperationsDownloading removeObject:operation];
    [self startNextQueuedOperation];
    
    if (success) {
        [self.requestURLs removeObject:operation.request.URL];
        
        if ([self.delegate respondsToSelector:@selector(downloadManager:operationCompleted:)]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.delegate downloadManager:self operationCompleted:operation];
            });
        }
    }
    else {
        [self.requestOperationsPaused addObject:operation];
        NSLog(@"%@", operation.error);
        
        if ([self.delegate respondsToSelector:@selector(downloadManager:operationFailed:)]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.delegate downloadManager:self operationFailed:operation];
            });
        }
    }
}

#pragma mark - Queue Manage
- (void)startAll {
    // Paused => Queue
    [self.requestOperationsQueue unionSet:self.requestOperationsPaused];
    [self.requestOperationsPaused removeAllObjects];
    
    // Queue => Start
    while (self.requestOperationsDownloading.count < _maxRunningTaskCount) {
        AFDownloadRequestOperation *operation = [self.requestOperationsQueue anyObject];
        if (!operation) break;
        
        [self startOperation:operation];
    }
}

- (void)pauseAll
{
    AFDownloadRequestOperation *operation;
    // Downloading => Pause
    while ((operation = [self.requestOperationsDownloading anyObject])) {
        [self pauseOperation:operation];
    }
    
    // Queue => Pause
    [self.requestOperationsPaused unionSet:self.requestOperationsQueue];
    [self.requestOperationsQueue removeAllObjects];
}

- (void)cancelAll
{
    AFDownloadRequestOperation *operation;
    while ((operation = [self.requestOperationsPaused anyObject])) {
        [self cancelOperation:operation];
    }
    while ((operation = [self.requestOperationsQueue anyObject])) {
        [self cancelOperation:operation];
    }
    while ((operation = [self.requestOperationsDownloading anyObject])) {
        [self cancelOperation:operation];
    }
}

- (void)startNextQueuedOperation
{
    if (self.requestOperationsQueue.count > 0 && self.requestOperationsDownloading.count < self.maxRunningTaskCount) {
        AFDownloadRequestOperation *operationNext = [self.requestOperationsQueue anyObject];
        [self startOperation:operationNext];
    }
}

// Note: Manager Queue
- (BOOL)startOperation:(AFDownloadRequestOperation *)operation {
    if (!operation) {
        NSLog(@"JSDownloadManager > startOperation: operation is nil");
        return NO;
    }
    
    [self.requestOperationsPaused removeObject:operation];
    if (self.requestOperationsDownloading.count < self.maxRunningTaskCount) {
        // Start Downloading
        if ([operation isPaused]) {
            [operation resume];
        }
        else {
            [operation start];
        }
        
        [self.requestOperationsDownloading addObject:operation];
        [self.requestOperationsQueue removeObject:operation];
    }else {
        // Add operation to Queue
        [self.requestOperationsQueue addObject:operation];
    }
    
    return YES;
}

- (BOOL)pauseOperation:(AFDownloadRequestOperation *)operation
{
    if (!operation) {
        NSLog(@"JSDownloadManager > pauseOperation: operation is nil");
        return NO;
    }
    if (![operation isPaused]) {
        [operation pause];
        [self startNextQueuedOperation];
    }
    
    [self.requestOperationsPaused addObject:operation];
    [self.requestOperationsQueue removeObject:operation];
    [self.requestOperationsDownloading removeObject:operation];
    
    return YES;
}

- (BOOL)cancelOperation:(AFDownloadRequestOperation *)operation
{
    if (!operation) {
        NSLog(@"JSDownloadManager > cancelOperation: operation is nil");
        return NO;
    }
    [operation cancel];
    
    [self.requestURLs removeObject:operation.request.URL];
    [self.requestOperationsDownloading removeObject:operation];
    [self.requestOperationsQueue removeObject:operation];
    [self.requestOperationsPaused removeObject:operation];
    [self startNextQueuedOperation];
    
    return YES;
}

- (BOOL)startOperationWithURL:(NSURL *)url
{
    return [self startOperation:[self findOperationWithURL:url]];
}
- (BOOL)pauseOperationWithURL:(NSURL *)url
{
    return [self pauseOperation:[self findOperationWithURL:url]];
}
- (BOOL)cancelOperationWithURL:(NSURL *)url
{
    return [self cancelOperation:[self findOperationWithURL:url]];
}
@end
